#include <os/list.h>
#include <os/lock.h>
#include <os/sched.h>
#include <os/time.h>
#include <os/mm.h>
#include <screen.h>
#include <printk.h>
#include <assert.h>

#define TCB_NUM 2
pcb_t pcb[NUM_MAX_TASK];
pcb_t tcb[TCB_NUM];

#define SR_SIE   0x00000002 /* Previous Supervisor IE */
extern void ret_from_exception();

const ptr_t pid0_stack = INIT_KERNEL_STACK + PAGE_SIZE;
pcb_t pid0_pcb = {
    .pid = 0,
    .kernel_sp = (ptr_t)pid0_stack,
    .user_sp = (ptr_t)pid0_stack};

LIST_HEAD(ready_queue);
LIST_HEAD(sleep_queue);

/* current running task PCB */
pcb_t *volatile current_running;

/* global process id */
pid_t process_id = 1;

void do_scheduler(void)
{
    // TODO: [p2-task3] Check sleep queue to wake up PCBs
    check_sleeping();
    // TODO: [p2-task1] Modify the current_running pointer.
    if (current_running->status == TASK_RUNNING)
    {
        current_running->status = TASK_READY;
    }
    pcb_t *last_running = current_running;
    pcb_t *next_running = (pcb_t *)((char *)(de_queue(&ready_queue)) - 0x10);
    if (current_running != &pid0_pcb && current_running->status == TASK_READY)
        en_queue(&ready_queue, &last_running->list);
    // TODO: [p2-task1] switch_to current_running
    next_running->status = TASK_RUNNING;
    current_running = next_running;
    switch_to(last_running, next_running);
}

void do_sleep(uint32_t sleep_time)
{
    // TODO: [p2-task3] sleep(seconds)
    // NOTE: you can assume: 1 second = 1 `timebase` 
    // 1. block the current_runningticks
    // 2. set the wake up time for the blocked task
    // 3. reschedule because the current_running is blocked.
    current_running ->status = TASK_BLOCKED;
    current_running -> wakeup_time = get_timer()+sleep_time;
    en_queue(&sleep_queue, &current_running->list);

    do_scheduler();
}

void do_block(list_node_t *pcb_node, list_head *queue)
{
    // TODO: [p2-task2] block the pcb task into the block queue
    pcb_t *tmp = (pcb_t *)((char *)(pcb_node)-0x10);
    tmp->status = TASK_BLOCKED;
    en_queue(queue, pcb_node);

    do_scheduler();
}

void do_unblock(list_node_t *pcb_node)
{
    // TODO: [p2-task2] unblock the `pcb` from the block queue
    list_node_t *node = de_queue(pcb_node);
    pcb_t *cur_pcb = (pcb_t *)((char *)node - 0x10);
    cur_pcb->status = TASK_READY;
    en_queue(&ready_queue, node);
}

void initready_queue(list_head *head, int tasknum)
{
    for(int i=0;i<tasknum;i++){
        en_queue(head,&pcb[i].list);
    }
}
list_node_t *de_queue(list_head *head)
{
    list_node_t *pre = head->prev;
    list_node_t *next = head->next;
    list_node_t *tmp = next->next;
    head->next = tmp;
    tmp->prev = head;
    return next;
}
void en_queue(list_head *head, list_node_t *p)
{
    list_node_t *pre = head->prev;
    head->prev = p;
    p->next = head;
    pre->next = p;
    p->prev = pre;
   // printl("addr is %x\n",pre);

}
int isempyt_queue(list_head *head)
{
    if (head->next == head && head->prev == head)
        return 1;
    return 0;
}
void thread_create(ptr_t func, int pid){
    int numpage = 1;
    ptr_t kernel_stack, user_stcak;

    kernel_stack = allocKernelPage(numpage);
    user_stcak = allocUserPage(numpage);
   
   //initialize the user_stack and kernel_stack
    regs_context_t *pt_regs =
        (regs_context_t *)((ptr_t)(kernel_stack + numpage * PAGE_SIZE) - sizeof(regs_context_t));
    for (int i = 0; i < 32; i++)
    {
        pt_regs->regs[i] = 0;
    }
    pt_regs->regs[1] = func;
    pt_regs->regs[2] = user_stcak + numpage * PAGE_SIZE;
    pt_regs->sstatus = SR_SIE;
    pt_regs->sepc    = func;
    
    switchto_context_t *pt_switchto =
        (switchto_context_t *)((ptr_t)pt_regs - sizeof(switchto_context_t));
    
    for (int i = 0; i < 14; i++)
    {
        pt_switchto->regs[i] = 0;
    }
    pt_switchto->regs[0] = ret_from_exception;
    pt_switchto->regs[1] = pt_switchto;

    tcb[pid-1].pid = pid;
    tcb[pid-1].status = TASK_READY;
    tcb[pid-1].cursor_x = 0;
    tcb[pid-1].cursor_y = 0;
    tcb[pid-1].wakeup_time = 0;
    tcb[pid-1].kernel_sp = pt_switchto;
    tcb[pid-1].user_sp   = (user_stcak + numpage * PAGE_SIZE);
    
    en_queue(&ready_queue, &tcb[pid-1].list);
}
